package org.finesys.gateway.filter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.finesys.common.core.module.R;
import org.finesys.gateway.properties.GatewayConfigProperties;
import org.finesys.gateway.util.WebFluxUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;

import static org.finesys.common.constants.BaseConstant.*;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;

@Component
@Slf4j
@RequiredArgsConstructor
public class GatewayRequestFilter implements GlobalFilter, Ordered {
    private final GatewayConfigProperties gatewayConfigProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    // nginx需要配置
    private static final String X_REAL_IP = "X-Real-IP";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 禁止客户端的访问资源逻辑
        Mono<Void> checkForbidUriResult = checkForbidUri(request, response);
        if (Objects.nonNull(checkForbidUriResult)) {
            return checkForbidUriResult;
        }
        //方法倒计时
        exchange.getAttributes().put(REQUEST_START_TIME, System.currentTimeMillis());
        // 标记请求来自网关
        ServerHttpRequest build = request.mutate().header(GATEWAY_TOKEN_HEADER, new String(Base64Utils.encode(GATEWAY_TOKEN_VALUE.getBytes()))).build();
        // then方法相当于aop的后置通知一样
        return chain.filter(exchange.mutate().request(build).build()).then(Mono.fromRunnable(() -> {
            printLog(exchange);
        }));
    }

    /**
     * 检验url是否禁止访问
     */
    private Mono<Void> checkForbidUri(ServerHttpRequest request, ServerHttpResponse response) {
        String uri = request.getPath().toString();
        List<String> forbidUris = gatewayConfigProperties.getForbidRequestUri();
        boolean shouldForward = true;
        if (!ObjectUtils.isEmpty(forbidUris)) {
            for (String u : forbidUris) {
                if (antPathMatcher.match(u, uri)) {
                    shouldForward = false;
                    break;
                }
            }
        }
        if (!shouldForward) {
            return WebFluxUtil.webFluxResponseWriter(response, R.failed("该URI不允许外部访问"), HttpStatus.FORBIDDEN.value());
        }
        return null;
    }

    /**
     * 打印执行日志
     */
    private void printLog(ServerWebExchange exchange) {
        URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        LinkedHashSet<URI> uris = exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        Long startTime = exchange.getAttribute(REQUEST_START_TIME);
        Long executeTime = 0L;
        if (startTime != null) {
            executeTime = (System.currentTimeMillis() - startTime);
        }
        List<String> ips = exchange.getRequest().getHeaders().get(X_REAL_IP);
        String ip = ips != null ? ips.get(0) : null;
        URI originUri = null;
        if (uris != null) {
            originUri = uris.stream().findFirst().orElse(null);
        }
        if (uri != null && route != null && originUri != null) {
            log.info("转发来自IP：{}的请求：{}://{}{} --> 目标服务：{}，目标地址：{}://{}{}，转发时间：{}，方法耗时：{}ms", ip, originUri.getScheme(), originUri.getAuthority(), originUri.getPath(), route.getId(), uri.getScheme(), uri.getAuthority(), uri.getPath(), LocalDateTime.now(), executeTime);
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
